package com.lightsaber.trade.as.service.category.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springside.modules.orm.Page;

import com.lightsaber.trade.as.dao.category.CategoryDao;
import com.lightsaber.trade.as.dao.category.CategoryPlannerDao;
import com.lightsaber.trade.as.dao.planning.CategoryPlanningItemDao;
import com.lightsaber.trade.as.dao.planning.DisciplinePlanningItemDao;
import com.lightsaber.trade.as.dao.planning.Level1PlanningDao;
import com.lightsaber.trade.as.entity.category.Category;
import com.lightsaber.trade.as.entity.category.CategoryBranch;
import com.lightsaber.trade.as.entity.category.CategoryLeaf;
import com.lightsaber.trade.as.entity.category.CategoryNode;
import com.lightsaber.trade.as.entity.category.InputType;
import com.lightsaber.trade.as.entity.category.SourceType;
import com.lightsaber.trade.as.entity.planning.CategoryPlanningItem;
import com.lightsaber.trade.as.entity.planning.DisciplinePlanningItem;
import com.lightsaber.trade.as.entity.planning.Level1Planning;
import com.lightsaber.trade.as.entity.planning.Level2Planning;
import com.lightsaber.trade.as.entity.planning.Level3Planning;
import com.lightsaber.trade.as.entity.planning.Planning;
import com.lightsaber.trade.as.entity.planning.PlanningLevel;
import com.lightsaber.trade.as.entity.planning.PlanningStatus;
import com.lightsaber.trade.as.service.category.CategoryNodeService;
import com.lightsaber.trade.as.service.category.CategoryPlannerService;
import com.lightsaber.trade.as.service.category.CategoryProgrammeAttributeService;
import com.lightsaber.trade.as.service.category.CategoryService;
import com.lightsaber.trade.as.service.param.ParamObject;
import com.lightsaber.trade.as.service.param.impl.ParamObjectImpl;
import com.lightsaber.trade.as.service.planning.Level2PlanningService;
import com.lightsaber.trade.as.service.planning.Level3PlanningService;
import com.lightsaber.trade.as.service.planning.PlanningService;
import com.lightsaber.trade.core.dao.BaseDao;
import com.lightsaber.trade.core.entity.AuditedObject;
import com.lightsaber.trade.core.entity.BaseEntity;
import com.lightsaber.trade.core.service.BaseServiceImpl;

@Component
@Transactional
public class CategoryServiceImpl extends BaseServiceImpl<Category, String> implements
        CategoryService {

    private static final Logger log = LoggerFactory.getLogger(CategoryServiceImpl.class);

    @Override
    protected BaseDao<Category, String> getDao() {
        return categoryDao;
    }

    @Autowired
    private transient Level2PlanningService level2PlanningService;
    @Autowired
    private transient Level3PlanningService level3PlanningService;
    @Autowired
    Level1PlanningDao level1PlanningDao;
    @Autowired
    private transient CategoryDao categoryDao;
    @Autowired
    private transient CategoryNodeService categoryNodeService;
    @Autowired
    private transient CategoryProgrammeAttributeService cpaService;
    @Autowired
    CategoryPlannerDao categoryPlannerDao;
    @Autowired
    CategoryPlannerService categoryPlannerService;
    @Autowired
    private transient PlanningService planningService;
    @Autowired
    private CategoryPlanningItemDao categoryPlanningItemDao;
    @Autowired
    private DisciplinePlanningItemDao disciplinePlanningItemDao;

    @Override
    public List<Category> search(final Category category) {
        return categoryDao.findByCategory(category);

    }

    @Override
    public Category findMasterTreeByPlanYear(final Integer planYear) throws Exception {
        return categoryDao.findMasterTreeByPlanYear(planYear);
    }

    @Override
    public Category findById(final String id) {
        return categoryDao.load(id);
    }

    @Override
    public void deleteById(final String id) throws Exception {
        Category category = findById(id);
        if (StringUtils.isBlank(category.getBranchName())) {
            List<Planning> planningList = planningService.findByCategoryId(id);
            boolean enableDelete = true;
            for (Planning planning : planningList) {
                if (planning.getStatus().equals(PlanningStatus.WaitingForEndorsement)
                        || planning.getStatus().equals(PlanningStatus.Endorsed)
                        || planning.getStatus().equals(PlanningStatus.NotEndorsed)
                        || planning.getStatus().equals(PlanningStatus.Unlocked)) {
                    enableDelete = false;
                    break;
                }
            }
            if (enableDelete) {
                deleteBranchTreeByCategory(category.getRoot().getChildren());
                categoryDao.delete(id);
            } else {
                throw new Exception("This Category has relationship with Level Planning.");
            }
        } else {
            Level1Planning level1Planning = category.getLevel1Planning();
            boolean enableDelete = true;
            if (level1Planning != null) {
                if (level1Planning.getStatus().equals(PlanningStatus.WaitingForEndorsement)
                        || level1Planning.getStatus().equals(PlanningStatus.Endorsed)
                        || level1Planning.getStatus().equals(PlanningStatus.NotEndorsed)
                        || level1Planning.getStatus().equals(PlanningStatus.Unlocked)) {
                    enableDelete = false;
                }
            }
            if (enableDelete) {
                // level1PlanningDao.delete(level1Planning);
                // categoryNodeDao.delete(category.getRoot());
                CategoryNode node = category.getOwnerNode();
                if (node.getOwnedBranchTrees().size() > 1) {
                    // ownerService.deleteByCategoryNodeId(node.getId());
                    node.getOwnedBranchTrees().remove(category);
                    categoryDao.delete(category.getId());
                } else {
                    throw new Exception(
                            "At least one Branch Tree should be kept and map to the node created by System Admin.");
                }
            } else {
                throw new Exception("This Category has relationship with Level Planning.");
            }
        }
    }

    public void deleteBranchTreeByCategory(final List<CategoryNode> children) {
        if (!children.isEmpty()) {
            for (int i = 0; i < children.size(); i++) {
                CategoryNode child = children.get(i);
                if (child.getChildren().isEmpty()) {
                    List<Category> categoryList = child.getOwnedBranchTrees();
                    for (Category category : categoryList) {
                        categoryDao.delete(category);
                    }
                } else {
                    deleteBranchTreeByCategory(child.getChildren());
                }
            }
        }
    }

    @Override
    public Page<Category> findPage(final Category category, final Page<Category> page,
            final String[] roles, final boolean isAdmin) {
        return categoryDao.findPage(category, page, roles, isAdmin);
    }

    @Override
    public void createCategoryFromPage(final Category category) throws Exception {
        validateCategory(category);
        if (StringUtils.isBlank(category.getId())) {
            categoryDao.save(category);

            if (category.getOwnerNode() == null) {
                createForMasterTree(category);
            }
        }
    }

    @Override
    public void saveCategory(final Category category) throws Exception {
        validateCategory(category);
        if (StringUtils.isBlank(category.getId())) {
            categoryDao.save(category);

        } else {
            Category old = findById(category.getId());
            if (!old.getPlanningExerciseName().equals(category.getPlanningExerciseName())) {
                changeBranchTreeName(category.getPlanningExerciseName(), old.getRoot()
                        .getChildren());
            }
            BeanUtils.copyProperties(category, old, new String[] { BaseEntity.ID, BaseEntity.VERSION,
                    AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE, "root", "ownerNode",
                    "level1Planning", "planningList", "archivedImageList" });
            categoryDao.update(old);

        }

    }

    public void changeBranchTreeName(final String planningExerciseName,
            final List<CategoryNode> children) {
        if (!children.isEmpty()) {
            for (int i = 0; i < children.size(); i++) {
                CategoryNode child = children.get(i);
                if (child.getChildren().isEmpty()) {
                    List<Category> categoryList = child.getOwnedBranchTrees();
                    for (Category category : categoryList) {
                        category.setPlanningExerciseName(planningExerciseName);
                        categoryDao.save(category);
                    }
                } else {
                    changeBranchTreeName(planningExerciseName, child.getChildren());
                }
            }
        }
    }

    @Override
    public void createForMasterTree(final Category category) {
        try {
            CategoryNode root = new CategoryBranch();
            root.setName("Root");
            root.setInputType(InputType.Both);
            root.setIsDisciplineView(0);
            root.setSourceType(SourceType.ManualInput);
            root.setIsInheritParentNode(0);
            root.setCategory(category);
            categoryNodeService.saveRoot(root);

            Planning planning1 = new Planning();
            planning1.setStatus(PlanningStatus.WaitingForSubmission);
            planning1.setCategory(category);
            planning1.setPlanningLevel(PlanningLevel.FirstLevel);
            planning1.setIsArchivedImage(false);
            planningService.save(planning1);

            Planning planning2 = new Planning();
            planning2.setStatus(PlanningStatus.WaitingForSubmission);
            planning2.setCategory(category);
            planning2.setPlanningLevel(PlanningLevel.SecondLevel);
            planning2.setIsArchivedImage(false);
            planningService.save(planning2);

            Level2Planning level2Planning = new Level2Planning();
            level2Planning.setPlanning(planning2);
            level2Planning.setPlanningExercise(category.getPlanYear());
            level2Planning.setRemarks(category.getRemarks());
            level2PlanningService.save(level2Planning);

            level2PlanningService.generalDefaultValue(level2Planning);

            Planning planning3 = new Planning();
            planning3.setStatus(PlanningStatus.WaitingForSubmission);
            planning3.setCategory(category);
            planning3.setPlanningLevel(PlanningLevel.ThirdLevel);
            planning3.setIsArchivedImage(false);
            planningService.save(planning3);

            Level3Planning level3Planning = new Level3Planning();
            level3Planning.setPlanning(planning3);
            level3Planning.setPlanningExercise(category.getPlanYear());
            level3Planning.setRemarks(category.getRemarks());
            level3PlanningService.save(level3Planning);
        } catch (Exception e) {
            log.error("generalDefaultValue error : {} ", e);
        }

    }

    private Category saveAsNew(final String categoryId, final String branchName,
            final Map<String, String> idMap) throws Exception {
        Category oldCategory = findById(categoryId);

        long cnt = categoryDao.findByOwnerNodeAndBranchName(oldCategory.getOwnerNode().getId(),
                branchName);
        if (cnt > 0) {
            throw new Exception(
                    "’Branch Name’ exists under the same category tree path, please check.");
        }

        // copy category
        Category newCategory = new Category();
        BeanUtils.copyProperties(oldCategory, newCategory, new String[] { BaseEntity.ID,
                BaseEntity.VERSION, AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                "branchName", "root", "level1Planning", "planningList", "archivedImageList" });
        newCategory.setBranchName(branchName);
        categoryDao.save(newCategory);
        // copy category node
        copyTree(null, categoryId, newCategory, null, null, idMap);

        return newCategory;

    }

    @Override
    public Category saveAsNew(final String categoryId, final String branchName) throws Exception {
        Map<String, String> idMap = new HashMap<String, String>();

        return saveAsNew(categoryId, branchName, idMap);

        // return newCategory;
    }

    @Override
    public Category saveAsNewWithPlanning(final String categoryId, final String branchName)
            throws Exception {
        Map<String, String> idMap = new HashMap<String, String>();

        Category oldCategory = findById(categoryId);
        Category newCategory = this.saveAsNew(categoryId, branchName, idMap);

        // 1. Copy Level1Planning
        Level1Planning oldLevel1Planning = level1PlanningDao.findByBranchTree(oldCategory);
        Level1Planning newLevel1Planning = new Level1Planning();

        BeanUtils.copyProperties(oldLevel1Planning, newLevel1Planning,
                new String[] { BaseEntity.ID, BaseEntity.VERSION, AuditedObject.CREATED_BY,
                        AuditedObject.CREATED_DATE, "planning", "category",
                        "categoryPlanningItems", "disciplinePlanningItems", "name" });

        newLevel1Planning.setName(newCategory.getBranchName() + "-planning");
        newLevel1Planning.setCategory(newCategory);
        newLevel1Planning.setStatus(PlanningStatus.WaitingForSubmission);
        level1PlanningDao.save(newLevel1Planning);

        // 2. Copy CategoryPlanningItems
        for (CategoryPlanningItem oldItem : oldLevel1Planning.getCategoryPlanningItems()) {
            CategoryPlanningItem newItem = new CategoryPlanningItem();

            BeanUtils.copyProperties(oldItem, newItem, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                    "level1Planning", "categoryLeaf" });

            newItem.setLevel1Planning(newLevel1Planning);
            String newLeafId = idMap.get(oldItem.getCategoryLeaf().getId());
            newItem.setCategoryLeaf(categoryNodeService.findLeafById(newLeafId));
            categoryPlanningItemDao.save(newItem);
        }

        // 3. Copy DisciplinePlanningItems
        for (DisciplinePlanningItem oldItem : oldLevel1Planning.getDisciplinePlanningItems()) {
            DisciplinePlanningItem newItem = new DisciplinePlanningItem();

            BeanUtils.copyProperties(oldItem, newItem, new String[] { BaseEntity.ID,
                    BaseEntity.VERSION, AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                    "level1Planning", "categoryNode" });

            newItem.setLevel1Planning(newLevel1Planning);
            String newNodeId = idMap.get(oldItem.getCategoryNode().getId());
            newItem.setCategoryNode(categoryNodeService.findById(newNodeId));
            disciplinePlanningItemDao.save(newItem);

        }

        return newCategory;
    }

    public void copyTree(final String parentId, final String oldCategoryId,
            final Category newCategory, final CategoryNode parentCategoryNode,
            final List<CategoryNode> oldChildList, final Map<String, String> idMap)
            throws Exception {

        if (StringUtils.isBlank(parentId)) {
            CategoryNode root = categoryNodeService.findByCategoryId(oldCategoryId);
            // CategoryNode root = currentList.get(0);
            CategoryNode newRoot = new CategoryBranch();
            BeanUtils.copyProperties(root, newRoot, new String[] { BaseEntity.ID, BaseEntity.VERSION,
                    "parent", "category", AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                    "categoryOwners", "categoryPlanners", "children", "ownedBranchTrees" });

            newRoot.setCategory(newCategory);

            categoryNodeService.saveRoot(newRoot);
            categoryPlannerService.copyPlannersByCategoryNode(root, newRoot);

            idMap.put(root.getId(), newRoot.getId());// Save id mapping

            copyTree(root.getId(), oldCategoryId, newCategory, newRoot, root.getChildren(), idMap);
        } else {
            if (!oldChildList.isEmpty()) {
                for (int i = 0; i < oldChildList.size(); i++) {
                    CategoryNode child = oldChildList.get(i);
                    if (child.getChildren().isEmpty()) {
                        if (child.getIsLeafNode() == 1) {
                            CategoryLeaf newLeaf = new CategoryLeaf();
                            BeanUtils.copyProperties(child, newLeaf, new String[] { BaseEntity.ID,
                                    BaseEntity.VERSION, "parent", "category",
                                    AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                                    "categoryOwners", "categoryPlanners", "children",
                                    "ownedBranchTrees", "categoryProgrammeAttributes",
                                    "categoryPlanningItems", "selectedPhraseOuts" });
                            newLeaf.setCategory(null);
                            newLeaf.setParent(parentCategoryNode);

                            categoryNodeService.saveLeaf(newLeaf);
                            categoryPlannerService.copyPlannersByCategoryNode(child, newLeaf);
                            cpaService.copyAttributeByCategoryNode((CategoryLeaf) child, newLeaf);
                            // Save id mapping
                            idMap.put(child.getId(), newLeaf.getId());

                        } else {
                            CategoryBranch newBranch = new CategoryBranch();
                            BeanUtils.copyProperties(child, newBranch, new String[] { BaseEntity.ID,
                                    BaseEntity.VERSION, "parent", "category",
                                    AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                                    "categoryOwners", "categoryPlanners", "children",
                                    "ownedBranchTrees" });
                            newBranch.setCategory(null);
                            newBranch.setParent(parentCategoryNode);

                            categoryNodeService.saveBranch(newBranch);
                            categoryPlannerService.copyPlannersByCategoryNode(child, newBranch);
                            // Save id mapping
                            idMap.put(child.getId(), newBranch.getId());
                        }

                    } else {
                        CategoryBranch newBranch = new CategoryBranch();
                        BeanUtils.copyProperties(child, newBranch, new String[] { BaseEntity.ID,
                                BaseEntity.VERSION, "parent", "category", AuditedObject.CREATED_BY,
                                AuditedObject.CREATED_DATE, "categoryOwners", "categoryPlanners",
                                "children", "ownedBranchTrees" });
                        newBranch.setCategory(null);
                        newBranch.setParent(parentCategoryNode);

                        categoryNodeService.saveBranch(newBranch);
                        categoryPlannerService.copyPlannersByCategoryNode(child, newBranch);
                        // Save id mapping
                        idMap.put(child.getId(), newBranch.getId());

                        copyTree(child.getId(), oldCategoryId, newCategory, newBranch,
                                child.getChildren(), idMap);
                    }
                }
            }
        }
    }

    @Override
    public List<Category> findBranchTreesByYearAndRole(final Integer exerciseYear, final String role) {
        return categoryDao.findBranchTreesByYearAndRole(exerciseYear, role);
    }

    /**
     * Find all branch trees for the planner
     * 
     * @param exerciseYear
     * @param role
     * @return
     */
    @Override
    public List<Category> findBranchTreesByYearAndRoleForPlanner(final Integer exerciseYear,
            final String role) {
        List<Category> categoryForPlannerList = new ArrayList<Category>();

        List<Category> categoryList = categoryDao.findBranchTreesByYear(exerciseYear);

        for (Category category : categoryList) {
            if (this.checkNodeByPlanner(category.getRoot(), role)) {
                categoryForPlannerList.add(category);
            }
        }

        return categoryForPlannerList;
    }

    private boolean checkNodeByPlanner(final CategoryNode node, final String role) {
        if (categoryPlannerDao.countBranchTreesByYear(node.getId(), role) > 0) {
            return true;
        }

        for (CategoryNode child : node.getChildren()) {
            if (checkNodeByPlanner(child, role)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Map<Integer, String> getAllCategoryOrderByYear() {
        List<Integer> yearList = categoryDao.getAllCategoryOrderByYear();
        Map<Integer, String> map = new LinkedHashMap<Integer, String>(yearList.size());
        for (Integer year : yearList) {
            String next = Integer.valueOf(year + 1).toString().substring(2);
            map.put(year, year + "/" + next);
            log.info("year : {}", year);
        }
        return map;
    }

    @Override
    public void validateCategory(final Category category) throws Exception {
        // if (category.getOwnerNode() == null) {
        // master tree
        if (StringUtils.isBlank(category.getId())) {
            if (StringUtils.isNotBlank(category.getBranchName())) {
                long cnt = categoryDao.findByOwnerNodeAndBranchName(
                        category.getOwnerNode().getId(), category.getBranchName());
                if (cnt > 0) {
                    throw new Exception(
                            "’Branch Name’ exists under the same category tree path, please check.");
                }
            } else {
                long list3 = categoryDao.findByExerciseNameForMasterTree(category
                        .getPlanningExerciseName());
                if (list3 > 0) {
                    throw new Exception("'Planning Exercise Name’ exist, please check.");
                }

                List<Category> list = new ArrayList<Category>();
                list = categoryDao.findByExerciseYearForMasterTree(category);
                if (!list.isEmpty()) {
                    throw new Exception("'Planning Exercise' exist, please check.");
                }
                if (category.getLevel1End() < category.getLevel1Start()) {
                    throw new Exception(
                            "The last year should be later then the first year of each level, please check.");
                }
                if (category.getLevel1End() > category.getLevel1Start() + 10) {
                    throw new Exception(
                            "The last year most ten years larger than the first year of each level, please check.");
                }
                if (category.getLevel2End() < category.getLevel2Start()) {
                    throw new Exception(
                            "The last year should be later then the first year of each level, please check.");
                }
                if (category.getLevel2End() > category.getLevel2Start() + 10) {
                    throw new Exception(
                            "The last year most ten years larger than the first year of each level, please check.");
                }
                if (category.getLevel3End() < category.getLevel3Start()) {
                    throw new Exception(
                            "The last year should be later then the first year of each level, please check.");
                }
                if (category.getLevel3End() > category.getLevel3Start() + 10) {
                    throw new Exception(
                            "The last year most ten years larger than the first year of each level, please check.");
                }

                if ((category.getPlanYear() > category.getLevel1End() || category.getPlanYear() < category
                        .getLevel1Start())
                        || ((category.getPlanYear() + 1) > category.getLevel1End() || (category
                                .getPlanYear() + 1) < category.getLevel1Start())) {
                    throw new Exception(
                            "The planning exercise doesn't exist between the last year and the first year of each level, please check.");
                }
                if ((category.getPlanYear() > category.getLevel2End() || category.getPlanYear() < category
                        .getLevel2Start())
                        || ((category.getPlanYear() + 1) > category.getLevel2End() || (category
                                .getPlanYear() + 1) < category.getLevel2Start())) {
                    throw new Exception(
                            "The planning exercise doesn't exist between the last year and the first year of each level, please check.");
                }
                if ((category.getPlanYear() > category.getLevel3End() || category.getPlanYear() < category
                        .getLevel3Start())
                        || ((category.getPlanYear() + 1) > category.getLevel3End() || (category
                                .getPlanYear() + 1) < category.getLevel3Start())) {
                    throw new Exception(
                            "The planning exercise doesn't exist between the last year and the first year of each level, please check.");
                }
            }
        } else {
            Category old = findById(category.getId());
            if (old.getOwnerNode() != null && StringUtils.isNotBlank(old.getBranchName())) {
                if (StringUtils.isBlank(category.getBranchName())) {
                    throw new Exception("'Branch Name’ is required.");
                }
                if (category.getBranchName().length() > 100) {
                    throw new Exception("The maximum length of ‘Branch Name’ is 100, please check.");
                }
                long list2 = categoryDao.findByOwnerNodeAndBranchName(category);
                if (list2 > 0) {
                    throw new Exception(
                            "’Branch Name’ exists under the same category tree path, please check.");
                }
            } else {
                long list = categoryDao.findByExerciseNameForMasterTree(category);
                if (list > 0) {
                    throw new Exception("'Planning Exercise Name’ exist, please check.");
                }
            }
        }
    }

    @Override
    public List<Category> getBranchTreesByRole(final String role) {
        return categoryDao.findByRole(role);
    }

    @Override
    public List<ParamObject> findMasterTrees() {
        List<Category> categoryList = categoryDao.findMasterTrees();

        List<ParamObject> planYearList = new ArrayList<ParamObject>();

        for (Category ca : categoryList) {
            planYearList.add(new ParamObjectImpl(String.valueOf(ca.getPlanYear()), ca
                    .getPlanningExerciseName()));
        }

        return planYearList;
    }

    @Override
    public List<ParamObject> getPlanYearWithSlashList() {

        List<Category> categoryList = categoryDao.findMasterTrees();

        List<ParamObject> planYearList = new ArrayList<ParamObject>();

        for (Category ca : categoryList) {

            String label = String.valueOf(ca.getPlanYear() + 1);

            planYearList.add(new ParamObjectImpl(String.valueOf(ca.getPlanYear()), String
                    .valueOf(ca.getPlanYear()) + "/" + label.substring(2)));
        }

        return planYearList;
    }

    @Override
    public List<Category> findMasterTreeList() {
        return categoryDao.findMasterTrees();
    }
}
